package org.gensokyo.shanbay

import java.time.LocalDate
import java.time.temporal.ChronoField

import com.gargoylesoftware.htmlunit.html._
import com.gargoylesoftware.htmlunit.{BrowserVersion, WebClient}
import com.typesafe.scalalogging.slf4j.StrictLogging
import org.apache.commons.codec.digest.DigestUtils
import org.gensokyo.shanbay.ShanBayClockOn.PageStatus.PageStatus
import org.gensokyo.shanbay.dao.UserInfoDao

import scala.annotation.tailrec
import scala.util.Random

/**
 * Created by liusen on 15-11-26.
 */
object ShanBayClockOn extends StrictLogging {

  private var errorFrequency: Int = 0

  object PageStatus extends Enumeration {
    type PageStatus = Value
    val UNKNOWN, START_REVIEW, RELOAD, WHETHER_KNOWN_WORD, KNOWN_WORD, SINGLE_NEXT_WORD, GROUP_NEXT_WORD, REVIEW_TASK_OVER, WAITING_FOR_CLOCK_ON, CLOCK_ON_OVER = Value
  }

  val DIV_REVIEW_BLOCK = ("div", "id", "review")
  val BUTTON_START_REVIEW = ("button", "class", "span12 btn-large btn-success start-review-button")
  val BUTTON_CLOCK_ON = ("button", "class", "btn btn-large btn-success")
  val ANCHOR_ALL_CLEAR = ("a", "class", "white-btn-large-left go-to-checkin-button")
  val ANCHOR_NEXT_WORD = ("a", "class", "continue continue-button")
  val ANCHOR_KNOWN_WORD = ("a", "class", "known span10")
  val ANCHOR_UNKNOWN_WORD = ("a", "class", "unknown span10")

  def checkPage(page: HtmlPage): PageStatus = {
    logger.debug("begin check page")
    Thread.sleep(3000L)
    val text = page.asText().replaceAll( """[ \n]+""", " ")
    val url = page.getUrl.toString
    logger.debug(s"page text:${text}")
    logger.debug(s"page url:${url}")

    if (text.contains("邮件验证")) {
      throw new Exception("需要邮件验证")
    }

    if (text.contains("正在加载数据") || text.contains("正在提交中") || text.contains("请稍候")) {
      var needReload: Boolean = false
      var i = 7
      try {
        while (i > 0) {
          i = i - 1
          val textWait = page.asText().replaceAll( """[ \n]+""", " ")
          logger.debug(s"textWait:${textWait}")
          if (text.contains("正在加载数据") || text.contains("正在提交中") || text.contains("请稍候")) {
            needReload = true
            Thread.sleep(1300L)
          } else {
            needReload = false
            throw new Exception("break")
          }
        }
      } catch {
        case e: Exception => logger.debug(e.getMessage)
      }
      logger.debug(s"needReload:${needReload}")
      if (needReload) {
        return PageStatus.RELOAD
      } else {
        return checkPage(page)
      }
    }

    if (url == "http://www.shanbay.com/bdc/review/") {
      if (text.contains("开始学习")) {
        return PageStatus.START_REVIEW
      } else if (text.contains("1. 认识 2. 不认识") || text.contains("1. 想起来了 2. 没想起来")) {
        return PageStatus.WHETHER_KNOWN_WORD
      } else if (text.contains("1. 查看详细")) {
        return PageStatus.KNOWN_WORD
      } else if (text.contains("单词解释")) {
        return PageStatus.SINGLE_NEXT_WORD
      } else if (text.contains("总结 快速回顾")) {
        return PageStatus.GROUP_NEXT_WORD
      } else if (text.contains("今日单词任务已完成")) {
        return PageStatus.REVIEW_TASK_OVER
      } else {
        logger.error("parser html error")
        return PageStatus.UNKNOWN
      }
    } else if (url == "http://www.shanbay.com/") {
      if (text.contains("天打卡 打卡")) {
        return PageStatus.WAITING_FOR_CLOCK_ON
      } else if (text.contains("分享打卡到新浪微博和微信")) {
        return PageStatus.CLOCK_ON_OVER
      } else {
        logger.error("parser html error")
        return PageStatus.UNKNOWN
      }
    } else return PageStatus.UNKNOWN
  }

  def getElement[T](page: HtmlPage, attrs: (String, String, String)): List[T] =
    page.getDocumentElement.getElementsByAttribute(attrs._1, attrs._2, attrs._3).toArray.toList.map(_.asInstanceOf[T])

  implicit def addMethodToList[T](l: List[T]): Object {def secondElementInTwo: T; def firstElementInTwo: T; def headElement: T} = new {
    def headElement: T = {
      if (l.size != 1) throw new Exception(s"get firstElement error , list:${l}")
      l.head
    }

    def firstElementInTwo: T = {
      if (l.size != 2) throw new Exception(s"get firstElementInTwo error , list:${l}")
      l.head
    }

    def secondElementInTwo: T = {
      if (l.size != 2) throw new Exception(s"get secondElementInTwo error , list:${l}")
      l.apply(1)
    }
  }

  @tailrec
  def handlePage(webClient: WebClient, page: HtmlPage): Boolean = {
    checkPage(page) match {
      case PageStatus.START_REVIEW => {
        logger.debug("shanbay 开始复习")
        handlePage(webClient, getElement[HtmlButton](page,BUTTON_START_REVIEW).headElement.click())
      }
      case PageStatus.WHETHER_KNOWN_WORD => {
        errorFrequency = errorFrequency + 1
        logger.debug(s"shanbay errorFrequency:${errorFrequency}")
        Thread.sleep(2000 + Random.nextInt(2000))
        if (errorFrequency % 30 == 6) {
          logger.debug("shanbay 不认识单词")
          handlePage(webClient, getElement[HtmlAnchor](page,ANCHOR_UNKNOWN_WORD).head.click())
        } else {
          logger.debug("shanbay 认识单词")
          handlePage(webClient, getElement[HtmlAnchor](page,ANCHOR_KNOWN_WORD).head.click())
        }
      }
      case PageStatus.KNOWN_WORD => {
        logger.debug("shanbay 查看详细")
        handlePage(webClient, getElement[HtmlAnchor](page,ANCHOR_KNOWN_WORD).head.click())
      }
      case PageStatus.SINGLE_NEXT_WORD => {
        logger.debug("shanbay 单个单词下一个")
        handlePage(webClient, getElement[HtmlAnchor](page,ANCHOR_NEXT_WORD).headElement.click())
      }
      case PageStatus.GROUP_NEXT_WORD => {
        logger.debug("shanbay 一组单词下一个")
        handlePage(webClient, getElement[HtmlAnchor](page,ANCHOR_NEXT_WORD).secondElementInTwo.click())
      }
      case PageStatus.REVIEW_TASK_OVER => {
        logger.debug("shanbay 今日单词任务已完成")
        val newPage: HtmlPage = webClient.getPage("http://www.shanbay.com/")
        handlePage(webClient, newPage)
      }
      case PageStatus.WAITING_FOR_CLOCK_ON => {
        logger.debug("shanbay 准备点击打卡按钮")
        handlePage(webClient, getElement[HtmlButton](page,BUTTON_CLOCK_ON).headElement.click())
      }
      case PageStatus.CLOCK_ON_OVER => {
        logger.debug("shanbay 打卡完毕")
        true
      }
      case PageStatus.RELOAD => {
        logger.debug("shanbay 重载页面")
        val newPage: HtmlPage = webClient.getPage(page.getUrl)
        handlePage(webClient, newPage)
      }
      case PageStatus.UNKNOWN => {
        logger.error("shanbay 页面解析错误")
        false
      }
    }

  }

  def clockOn(useName: String, password: String): Boolean = {
    var page: HtmlPage = null
    var webClient: WebClient = null
    try {
      webClient = new WebClient(BrowserVersion.FIREFOX_38)

      val pageLogin: com.gargoylesoftware.htmlunit.html.HtmlPage = webClient.getPage("http://www.shanbay.com/accounts/login/")

      val text = pageLogin.asText().replaceAll( """[ \n]+""", " ")
      logger.debug("text:" + text)
      if (text.contains("验证码")) throw new Exception("shanbay 验证码")

      val htmlForm = pageLogin.getDocumentElement.getElementsByAttribute[HtmlForm] ("form", "id", "loginform").toArray.toList.head.asInstanceOf[HtmlForm]
      val usernameTextField = htmlForm.getInputByName("username").asInstanceOf[HtmlTextInput]
      val passwordTextField = htmlForm.getInputByName("password").asInstanceOf[HtmlPasswordInput]
      val button = htmlForm.getElementsByAttribute("button", "type", "submit").toArray.toList.head.asInstanceOf[HtmlButton]

      usernameTextField.setValueAttribute(useName)
      passwordTextField.setValueAttribute(password)
      page = button.click()

      logger.debug("text:" + page.asText().replaceAll( """[ \n]+""", " "))

      page = webClient.getPage("http://www.shanbay.com/bdc/review/")
      Thread.sleep(2000)
      return handlePage(webClient, page)
    } catch {
      case e: Exception => {
        logger.error(e.getMessage, e)
        return false
      }
    } finally {
      webClient.close()
    }
  }

  def deal(): Unit = {
    val res = try {
      //            val (oldNameList, newNameList) = UserInfoDao.groupedUserList
      //            (oldNameList
      //              .sortBy(x => DigestUtils.md5Hex(x._1 + LocalDate.now().getDayOfYear.toString))
      //              .slice(0, 8 - newNameList.size) ::: newNameList
      //              ).filter(x => x._4.getLong(ChronoField.EPOCH_DAY) < LocalDate.now().getLong(ChronoField.EPOCH_DAY))
      //              .sortBy(x => DigestUtils.sha1Hex(x._1 + LocalDate.now().getDayOfYear.toString))
      UserInfoDao.getAllUsers.map(x => {
        val name = x._1
        val password = x._2
        logger.debug(s"start clock on name:${name} password:${password}")
        val result = clockOn(x._1, x._2)
        logger.debug(s"clock over name:${name} password:${password} result:${result}")
        //        if (result) RedisClient.add(name)
        if (result) UserInfoDao.updateDaysAndDate(name)
        val sleepTime = (600 + new Random().nextInt(800)) * 1000L
        logger.debug(s"begin to sleep:${sleepTime} ms")
        Thread.sleep(sleepTime)
        logger.debug("sleep over")
        (x._1, x._2, result)
      })
    } catch {
      case e: Exception => {
        logger.error(e.getMessage, e)
        List.empty
      }
    }
    logger.info(s"res:${res}")
  }

  def main(args: Array[String]) {
    deal()
  }

}
